home *** CD-ROM | disk | FTP | other *** search
Wrap
Text File | 2009-12-16 | 63.6 KB | 1,727 lines
const EXPORTED_SYMBOLS = ["CookiesService"]; // First, start with some various debug functions const DIRSERVICE = Components.classes['@mozilla.org/file/directory_service;1'].getService(Components.interfaces.nsIProperties); var logFile = DIRSERVICE.get('ProfDS', Components.interfaces.nsIFile); logFile.append("cookies.log"); var fileOutputStream = Components.classes['@mozilla.org/network/file-output-stream;1'].createInstance(Components.interfaces.nsIFileOutputStream); fileOutputStream.init(logFile, 0x02 | 0x08 | 0x10, 0600, 0); var converter = Components.classes['@mozilla.org/intl/converter-output-stream;1'].createInstance(Components.interfaces.nsIConverterOutputStream); converter.init(fileOutputStream, 'UTF-8', 1024, '-'); function log(component,msg) { return; /* converter.writeString(component+" - "+msg+"\n"); converter.flush(); //fileOutputStream.close(); return; */ if (msg) Components.utils.reportError("Cookies::"+component+" - "+msg); else Components.utils.reportError("Cookies - "+msg); } function inspect(obj) { var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator); var win = wm.getMostRecentWindow("navigator:browser"); if (!win.inspectObject) return; win.inspectObject(obj); } function alert(m) { var hiddenWindow = Components.classes["@mozilla.org/appshell/appShellService;1"] .getService(Components.interfaces.nsIAppShellService) .hiddenDOMWindow; hiddenWindow.alert(m); } /////////////////////////////////////////////////////////////////////////// // Class Cookie //------------------------------------------------------------------------- // Represent *one* cookie key/value // Have two static methods : cookiesListToString and parseCookieDate // function Cookie(cookieAttributes) { this.name = cookieAttributes.name; this.value = cookieAttributes.value; this.host = cookieAttributes.host; this.path = cookieAttributes.path; this.isSecure = cookieAttributes.isSecure; this.isHttpOnly = cookieAttributes.isHttpOnly; this.expires = cookieAttributes.expires; this.maxage = cookieAttributes.maxage; } Cookie.parseCookieDate = function (s) { // note: static function ... return new Date(s.replace(/-/g," ")); } Cookie.cookiesListToString = function (list) { var cookieData = ""; for (var i = 0; i < list.length; ++i) { var cookie = list[i]; // check if we have anything to write if (cookie.name.length>0 || cookie.value.length>0) { // if we've already added a cookie to the return list, append a "; " so // that subsequent cookies are delimited in the final list. if (cookieData.length>0) { cookieData += "; "; } if (cookie.name.length>0) { // we have a name and value - write both cookieData += cookie.name + "=" + cookie.value; } else { // just write value cookieData += cookie.value; } } } return cookieData; } const mTLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"] .getService(Components.interfaces.nsIEffectiveTLDService); Cookie.prototype._checkDomain = function(uri) { // JS equivalent of CheckDomain // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#1932 var hostFromURI = uri.asciiHost; if (!hostFromURI) return false; // trim trailing dots hostFromURI = hostFromURI.replace(/(^\.)|(\.$)/g,""); // if a domain is given, check the host has permission if (this.host && this.host.length>0) { // switch to lowercase now, to avoid case-insensitive compares everywhere this.host = this.host.replace(/(^\.)|(\.$)/g,"").toLowerCase(); // get the base domain for the host URI. // e.g. for "images.bbc.co.uk", this would be "bbc.co.uk", which // represents the lowest level domain a cookie can be set for. try { var baseDomain = mTLDService.getBaseDomain(uri); baseDomain = baseDomain.replace(/(^\.)|(\.$)/g,""); } catch(e) { // check whether the host is an IP address, and leave the cookie as // a non-domain one. this will require an exact host match for the cookie, // so we eliminate any chance of IP address funkiness (e.g. the alias 127.1 // domain-matching 99.54.127.1). bug 105917 originally noted the // requirement to deal with IP addresses. if (e.name=="NS_ERROR_HOST_IS_IP_ADDRESS") { return hostFromURI == this.host; } log("SetCondition","Ignore cookie on domain 1"); return false; } // ensure the proposed domain is derived from the base domain; and also // that the host domain is derived from the proposed domain (per RFC2109). // we prepend a dot before the comparison to ensure e.g. // "mybbc.co.uk" isn't matched as a superset of "bbc.co.uk". hostFromURI = "."+hostFromURI; this.host = "."+this.host; baseDomain = "."+baseDomain; function StringEndsWith(base, str) { return base.length >= str.length && base.lastIndexOf(str) + str.length == base.length } var accept = StringEndsWith(this.host, baseDomain) && StringEndsWith(hostFromURI, this.host); if (!accept) log("SetCondition","Ignore cookie on domain 2 : "+this.host+"/"+baseDomain+" -- "+hostFromURI+"/"+this.host); return accept; /* * note: RFC2109 section 4.3.2 requires that we check the following: * that the portion of host not in domain does not contain a dot. * this prevents hosts of the form x.y.co.nz from setting cookies in the * entire .co.nz domain. however, it's only a only a partial solution and * it breaks sites (IE doesn't enforce it), so we don't perform this check. */ } // block any URIs without a host that aren't file:/// URIs if (!hostFromURI || hostFromURI.length==0) { if (!uri.schemeIs("file")) { log("SetCondition","Ignore cookie on domain 3"); return false; } } // no domain specified, use hostFromURI this.host = hostFromURI; return true; } Cookie.prototype._checkPath = function(aHostURI) { // JS equivalent of CheckPath // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#2003 // if a path is given, check the host has permission if (!this.path || this.path.length==0) { log("checkPath","Empty path '"+this.path+"' -> compute automatically!"); // strip down everything after the last slash to get the path, // ignoring slashes in the query string part. // if we can QI to nsIURL, that'll take care of the query string portion. // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash. try { aHostURI.QueryInterface(Components.interfaces.nsIURL); this.path = aHostURI.directory; } catch(e) { this.path = aHostURI.path; var idx = this.path.lastIndexOf('/'); if (idx!=-1) this.path = this.path.substring(0,idx-1); } } if (this.path && this.path.length > 1024 || this.path.indexOf('\t')!=-1 ) return false; return true; } Cookie.prototype.canSetFrom = function (uri) { // JS equivalent of SetCookieInternal (part of) // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#1385 if (this.name.length + this.value.length >4096) { log("SetCondition","cookie too long"); return false; } if (this.name.indexOf('\t')!=-1) { log("SetCondition","invalid name character : \\t"); return false; } if (!this._checkDomain(uri)) { log("SetCondition","Refuse this domain!!!"); return false; } if (!this._checkPath(uri)) { log("SetCondition","Refuse this path!!!"); return false; } return true; } Cookie.prototype.computeExpiracy = function(serverTime) { // JS equivalent of GetExpiry // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#2048 // Compute expicary time var isSessionCookie=false; var delta; if (this.maxage && this.maxage.length>0) { try { delta = parseInt(this.maxage); } catch(e){ log("ComputeExpiracy","maxage not an int"); isSessionCookie = true; } } else if (this.expires && this.expires.length>0) { try { var temp = Cookie.parseCookieDate(this.expires); //Components.utils.reportError("got expires : "+temp.getTime()+" - "+serverTime); delta = (temp.getTime()) - serverTime; } catch(e){ log("ComputeExpiracy","expires not a valid date"); isSessionCookie = true; } } else { isSessionCookie = true; } if (delta) this.expiry = new Date().getTime()+delta; this.isSession = isSessionCookie; } Cookie.prototype.canGetFrom = function (pathFromURI, isSecure, aHttpBound) { // JS equivalent of GetCookieInternal (part of) // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#1276 function ispathdelimiter(c) { return c == '/' || c == '?' || c == '#' || c == ';'; } // if the cookie is secure and the host scheme isn't, we can't send it if (this.isSecure && !isSecure) { log("GetCondition","Ignore secure : "+this.name); return false; } // if the cookie is httpOnly and it's not going directly to the HTTP // connection, don't send it if (this.isHttpOnly && !aHttpBound) { log("GetCondition","Ignore httponly : "+this.name); return false; } // calculate cookie path length, excluding trailing '/' var cookiePathLen = this.path.length; if (cookiePathLen > 0 && this.path[cookiePathLen-1] == '/') { --cookiePathLen; } // if the nsIURI path is shorter than the cookie path, don't send it back if (pathFromURI.indexOf(this.path.substring(0,cookiePathLen))!=0) { log("GetCondition","Ignore on path : "+this.name+" -> "+pathFromURI+" -- "+this.path.substring(0,cookiePathLen)); return false; } if (pathFromURI.length > cookiePathLen && !ispathdelimiter(pathFromURI[cookiePathLen])) { /* * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'. * '/' is the "standard" case; the '?' test allows a site at host/abc?def * to receive a cookie that has a path attribute of abc. this seems * strange but at least one major site (citibank, bug 156725) depends * on it. The test for # and ; are put in to proactively avoid problems * with other sites - these are the only other chars allowed in the path. */ log("GetCondition","Ignore on path 2 : "+pathFromURI+"/"+this.path.substring(0,cookiePathLen)+" -- "+pathFromURI[cookiePathLen]); return false; } // check if the cookie has expired if (this.expiry && this.expiry <= new Date().getTime()) { log("GetCondition","Ignore expired : "+this.name); return false; } return true; } /////////////////////////////////////////////////////////////////////////// // Class CookieStringIterator //------------------------------------------------------------------------- // Iterator over a HTTP or JS cookie string // in order to return Cookies objects function CookieStringIterator(str) { this._hasNext = true; this._str = str; } CookieStringIterator.prototype.hasNext = function () { return this._hasNext; } CookieStringIterator.prototype._getCurrentString = function () { return this._str; } CookieStringIterator.prototype._setNewString = function (str, hasNext) { this._str = str; this._hasNext = hasNext; } CookieStringIterator._getTokenValue = function(str, startIndex) { // JS equivalent of GetTokenValue // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#1658 function iswhitespace (c) { return c == ' ' || c == '\t'; } function isterminator (c) { return c == '\n' || c == '\r'; } function isquoteterminator(c) { return isterminator(c) || c == '"'; } function isvalueseparator (c) { return isterminator(c) || c == ';'; } function istokenseparator (c) { return isvalueseparator(c) || c == '='; } var tokenString = "" var tokenValue = "" var lastIndex = str.length-1; var i = startIndex; // find <token>, including any <LWS> between the end-of-token and the // token separator. we'll remove trailing <LWS> next while (i != lastIndex && iswhitespace(str[i])) ++i; start = i; while (i != lastIndex && !istokenseparator(str[i])) ++i; // remove trailing <LWS>; first check we're not at the beginning lastSpace = i; if (lastSpace != start) { while (--lastSpace != start && iswhitespace(str[lastSpace])); ++lastSpace; } //log("Parse","start:"+start+" i:"+i+" lastSpace:"+lastSpace+"("+str[lastSpace]+") lastIndex:"+lastIndex+"("+str[lastIndex]+") lastIndex+1:"+str[lastIndex+1]); if (lastSpace==lastIndex && !istokenseparator(str[lastIndex])) tokenString = str.substring(start, lastIndex+1); else tokenString = str.substring(start, lastSpace); var equalsFound = (str[i] == '='); if (equalsFound) { //log("Parse","Equals found"); // find <value> while (++i != lastIndex && iswhitespace(str[i])); start = i; if (str[i] == '"') { //log("Parse","Quote mode"); // process <quoted-string> // (note: cookie terminators, CR | LF, can't happen: // they're removed by necko before the header gets here) // assume value mangled if no terminating '"', return while (++i != lastIndex && !isquoteterminator(str[i])) { // if <qdtext> (backwhacked char), skip over it. this allows '\"' in <quoted-string>. // we increment once over the backwhack, nullcheck, then continue to the 'while', // which increments over the backwhacked char. one exception - we don't allow // CR | LF here either (see above about necko) if (str[i] == '\\' && (++i == lastIndex || isterminator(str[i]))) break; } if (i != lastIndex && !isterminator(str[i])) { // include terminating quote in attribute string tokenValue = str.substring(start, ++i); // skip to next ';' while (i != lastIndex && !isvalueseparator(str[i])) ++i; } } else { // process <token-value> // just look for ';' to terminate ('=' allowed) while (i != lastIndex && !isvalueseparator(str[i])) ++i; //log("Parse","NewI:"+i+" start:"+start+" lastIndex:"+lastIndex+" str[i]:"+str[i]); // remove trailing <LWS>; first check we're not at the beginning //if (i != start) { lastSpace = i; while (--lastSpace != start && iswhitespace(str[lastSpace])) ; //log("Parse",i+"-"+lastSpace+"/"+lastIndex+"-"+str[lastSpace]+"/"+str[lastIndex]+"/"+str[lastIndex+1]); if (lastSpace+1==lastIndex && !isvalueseparator(str[lastIndex])) tokenValue = str.substring(start, lastIndex+1); else tokenValue = str.substring(start, ++lastSpace); //} } } var terminator = false; // i is on ';', or terminator, or EOS if (i != lastIndex) { // if on terminator, increment past & return PR_TRUE to process new cookie if (isterminator(str[i])) { ++i; terminator = true; } else { // fall-through: i is on ';', increment and return PR_FALSE ++i; } } var res= { tokenString: tokenString, tokenValue: tokenValue, newCookie: terminator, newIndex: i, equalsFound : equalsFound }; log("getTokenValue", tokenString+" = "+tokenValue); //log("getTokenValue","("+str+", "+startIndex+") --> "+res.toSource()); return res; } CookieStringIterator.prototype.getNext = function () { // JS equivalent of ParseAttributes function // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#1743 var str = this._getCurrentString(); var cookieStart = 0; var cookieEnd = str.length-1; var newCookie = false; var cookieAttributes = {}; cookieAttributes.isSecure = false; cookieAttributes.isHttpOnly = false; // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings. // if we find multiple cookies, return for processing // note: if there's no '=', we assume token is <VALUE>. this is required by // some sites (see bug 169091). // XXX fix the parser to parse according to <VALUE> grammar for this case var result = CookieStringIterator._getTokenValue(str, cookieStart); cookieStart = result.newIndex; newCookie = result.newCookie; if (result.equalsFound) { cookieAttributes.name = result.tokenString; cookieAttributes.value = result.tokenValue; } else { cookieAttributes.name = ""; cookieAttributes.value = result.tokenString; } // extract remaining attributes while (cookieStart != cookieEnd && !newCookie) { result = CookieStringIterator._getTokenValue(str, cookieStart); cookieStart = result.newIndex; newCookie = result.newCookie; if (result.tokenValue && result.tokenValue.length>0) { var tempEnd = result.tokenValue.length-1; if (result.tokenValue[0] == '"' && result.tokenValue[--tempEnd] == '"') { // our parameter is a quoted-string; remove quotes for later parsing result.tokenValue = result.tokenValue.substring(1,tempEnd); } } // decide which attribute we have, and copy the string if (result.tokenString.toLowerCase() == "path") cookieAttributes.path = result.tokenValue; else if (result.tokenString.toLowerCase() == "domain") cookieAttributes.host = result.tokenValue; else if (result.tokenString.toLowerCase() == "expires") cookieAttributes.expires = result.tokenValue; else if (result.tokenString.toLowerCase() == "max-age") cookieAttributes.maxage = result.tokenValue; // ignore any tokenValue for isSecure; just set the boolean else if (result.tokenString.toLowerCase() == "secure") cookieAttributes.isSecure = true; // ignore any tokenValue for isHttpOnly (see bug 178993); // just set the boolean else if (result.tokenString.toLowerCase() == "httponly") cookieAttributes.isHttpOnly = true; } // rebind aCookieHeader, in case we need to process another cookie this._setNewString(str.substring(cookieStart),newCookie); return new Cookie(cookieAttributes); } /////////////////////////////////////////////////////////////////////////// // Service CookiesStorage //------------------------------------------------------------------------- // Only handle effective storage to a sqlite database // var CookiesStorage = {} CookiesStorage._db = null; CookiesStorage._openDB = function () { try { var dbService = Components.classes["@mozilla.org/storage/service;1"].getService(Components.interfaces.mozIStorageService); var dirService = Components.classes['@mozilla.org/file/directory_service;1'].getService(Components.interfaces.nsIProperties); var dbFile = dirService.get("ProfDS", Components.interfaces.nsIFile); dbFile.append('yoono'); dbFile.append('cookies.sqlite'); if (!dbFile.exists()) dbFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0655); var dbConnection = dbService.openUnsharedDatabase(dbFile); this._db = dbConnection; //Components.utils.reportError("Current cookies DB version : "+dbConnection.schemaVersion); if (dbConnection.schemaVersion==1) { try { dbConnection.executeSimpleSQL("ALTER TABLE sessions ADD COLUMN network TEXT"); dbConnection.executeSimpleSQL("ALTER TABLE sessions ADD COLUMN network_id INTEGER"); } catch(e) { if (this._db) Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError+"\n"+e); else Components.utils.reportError(e); } dbConnection.schemaVersion = 2; } if (dbConnection.schemaVersion==2) { try { dbConnection.executeSimpleSQL("ALTER TABLE sessions ADD COLUMN home TEXT"); } catch(e) { if (this._db) Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError+"\n"+e); else Components.utils.reportError(e); } dbConnection.schemaVersion = 3; } var currentVersion = 3; if ( dbConnection.schemaVersion!=currentVersion ) { try { dbConnection.executeSimpleSQL("DROP TABLE cookies"); } catch(e) {} try { dbConnection.executeSimpleSQL("DROP TABLE sessions"); } catch(e) {} var createCookiesTable = "CREATE TABLE cookies ("; createCookiesTable += "session_id TEXT, name TEXT, value TEXT, "; createCookiesTable += "host TEXT, path TEXT, expiry INTEGER, "; createCookiesTable += "lastAccessed INTEGER, isSecure INTEGER, "; createCookiesTable += "isHttpOnly INTEGER, isSession INTEGER, "; createCookiesTable += "PRIMARY KEY (session_id, name, host, path) "; createCookiesTable += ")"; dbConnection.executeSimpleSQL(createCookiesTable); var createSessionsTable = "CREATE TABLE sessions ("; createSessionsTable += "id TEXT PRIMARY KEY, name TEXT, avatar BLOB, network TEXT, network_id INTEGER, home TEXT"; createSessionsTable += ")"; dbConnection.executeSimpleSQL(createSessionsTable); dbConnection.schemaVersion = currentVersion; return; // Don't do cleanup! } // Performances improvements dbConnection.executeSimpleSQL("PRAGMA synchronous = OFF"); // remove this when debugging in order to use sqlitemanager when firefox is launched dbConnection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE"); var cleanup1 = this._db.createStatement("DELETE FROM cookies WHERE session_id like 'temp_%'"); cleanup1.executeAsync(); var cleanup2 = this._db.createStatement("DELETE FROM sessions WHERE id like 'temp_%'"); cleanup2.executeAsync(); var cleanup3 = this._db.createStatement("DELETE FROM cookies WHERE expiry >0 and expiry<:expiry"); cleanup3.params.expiry = new Date().getTime(); cleanup3.executeAsync(); } catch(e) { if (this._db) Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError+"\n"+e); else Components.utils.reportError(e); } } CookiesStorage.deleteSession = function(id) { try { if (!this._db) this._openDB(); this._db.beginTransaction(); var statement = this._db.createStatement("DELETE FROM cookies WHERE session_id = :id"); statement.params.id = id; statement.executeStep(); var statement = this._db.createStatement("DELETE FROM sessions WHERE id = :id"); statement.params.id = id; statement.executeStep(); this._db.commitTransaction(); } catch(e) { try { Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError); } catch(e) { Components.utils.reportError(e); } } } CookiesStorage.renameSession = function(src, dst) { try { if (!this._db) this._openDB(); this._db.beginTransaction(); var statement = this._db.createStatement("DELETE FROM cookies WHERE session_id = :dst"); statement.params.dst = dst; statement.executeStep(); var statement = this._db.createStatement("DELETE FROM sessions WHERE id = :dst"); statement.params.dst = dst; statement.executeStep(); var statement = this._db.createStatement("UPDATE sessions SET id=:dst WHERE id = :src"); statement.params.src = src; statement.params.dst = dst; statement.executeStep(); var statement = this._db.createStatement("UPDATE cookies SET session_id=:dst WHERE session_id = :src"); statement.params.src = src; statement.params.dst = dst; statement.executeStep(); this._db.commitTransaction(); // TODO: do this anywhere else! var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] .getService(Components.interfaces.nsIWindowWatcher); var enumerator = ww.getWindowEnumerator(); while (enumerator.hasMoreElements()) { var win = enumerator.getNext(); if (!win.gBrowser) continue; var num = win.gBrowser.mPanelContainer.childNodes.length; for (var i = 0; i < num; i++) { var browser = win.gBrowser.getBrowserAtIndex(i); var tab = win.gBrowser.tabContainer.childNodes[i]; if (browser.__cookies && browser.__cookies._sessionId==src) { browser.__cookies._sessionId = dst; var ss = Components.classes["@mozilla.org/browser/sessionstore;1"] .getService(Components.interfaces.nsISessionStore); ss.setTabValue(tab, "cookies-db-id", dst); } } } if (this._updateBuffer[src]) this._updateBuffer[dst] = this._updateBuffer[src]; delete this._updateBuffer[src]; } catch(e) { try { Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError); } catch(e) { Components.utils.reportError(e); } } } CookiesStorage.setSessionInfo = function (sessionId, name, avatar, network, networkId, homepageURL) { try { if (!this._db) this._openDB(); var statement = this._db.createStatement("INSERT OR REPLACE INTO sessions (id, name, avatar, network, network_id, home) VALUES (:id, :name, :avatar, :network, :network_id, :home)"); statement.params.id = sessionId; statement.params.name = name || ""; statement.params.avatar = avatar || ""; statement.params.network = network || ""; statement.params.network_id = networkId || -1; statement.params.home = homepageURL || ""; statement.executeStep(); } catch(e) { if (this._db) Components.utils.reportError(sessionId+"-"+name+"-"+avatar+"-"+network+"-"+networkId+" "+this._db.lastErrorString+" / "+this._db.lastError+"\n"+e); else Components.utils.reportError(e); } } CookiesStorage.getSessionInfo = function (sessionId, callback) { try { var result; if (!this._db) this._openDB(); var statement = this._db.createStatement("SELECT * FROM sessions WHERE id = :id"); statement.params.id = sessionId; statement.executeAsync({ handleResult: function(aResultSet) { var row = aResultSet.getNextRow(); result = CookiesStorage._sqlToJSSession(row); }, handleError: function(aError) { callback(result); }, handleCompletion: function(aReason) { if (aReason != Components.interfaces.mozIStorageStatementCallback.REASON_FINISHED) { //print("Query canceled or aborted!"); } callback(result); } }); } catch(e) { try { Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError); } catch(e) { Components.utils.reportError(e); } } } CookiesStorage._sqlToJSSession = function (row) { return { id: row.getResultByName("id"), name: row.getResultByName("name"), avatar: row.getResultByName("avatar"), network: row.getResultByName("network"), networkId: row.getResultByName("network_id"), homepage: row.getResultByName("home") }; } CookiesStorage.getAllSessions = function (callback) { try { var results = []; if (!this._db) this._openDB(); var statement = this._db.createStatement("SELECT * FROM sessions"); statement.executeAsync({ handleResult: function(aResultSet) { while (row = aResultSet.getNextRow()) { results.push( CookiesStorage._sqlToJSSession(row) ); } }, handleError: function(aError) { callback(results); }, handleCompletion: function(aReason) { if (aReason != Components.interfaces.mozIStorageStatementCallback.REASON_FINISHED) { //print("Query canceled or aborted!"); } callback(results); } }); } catch(e) { try { Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError); } catch(e) { Components.utils.reportError(e); } } } CookiesStorage.getCookiesForSession = function (sessionId, callback) { try { var list = []; if (!this._db) this._openDB(); var statement = this._db.createStatement("SELECT * FROM cookies WHERE session_id = :session_id and (expiry=0 or expiry>=:expiry)"); statement.params.session_id = sessionId; statement.params.expiry = new Date().getTime(); statement.executeAsync({ handleResult: function(aResultSet) { var row; while (row = aResultSet.getNextRow()) { var cookie = new Cookie({ name: row.getResultByName("name"), value: row.getResultByName("value"), host: row.getResultByName("host"), path: row.getResultByName("path"), isSecure: row.getResultByName("isSecure")==1?true:false, isHttpOnly: row.getResultByName("isHttpOnly")==1?true:false, expires: null, maxage: null }); cookie.expiry = row.getResultByName("expiry"); if (cookie.expiry==0) cookie.expiry = null; cookie.isSession = row.getResultByName("isSession")==1?true:false; list.push(cookie); } }, handleError: function(aError) { //print("Error: " + aError.message); }, handleCompletion: function(aReason) { if (aReason != Components.interfaces.mozIStorageStatementCallback.REASON_FINISHED) { //print("Query canceled or aborted!"); } callback(list); } }); } catch(e) { try { Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError); } catch(e) { Components.utils.reportError(e); } } } CookiesStorage.deleteCookie = function (sessionId, cookie) { try { if (this._updateBuffer && this._updateBuffer[sessionId] && this._updateBuffer[sessionId][cookie.host] && this._updateBuffer[sessionId][cookie.host][cookie.path]) delete this._updateBuffer[sessionId][cookie.host][cookie.path][cookie.name]; return; if (cookie.name && cookie.name.length>0 && this.cookiesByDomain && this.cookiesByDomain[cookie.host] && this.cookiesByDomain[cookie.host][cookie.path] && this.cookiesByDomain[cookie.host][cookie.path].named && this.cookiesByDomain[cookie.host][cookie.path].named[cookie.name]) delete this.cookiesByDomain[cookie.host][cookie.path].named[cookie.name]; else if (this.cookiesByDomain && this.cookiesByDomain[cookie.host] && this.cookiesByDomain[cookie.host][cookie.path] && this.cookiesByDomain[cookie.host][cookie.path].unnamed && this.cookiesByDomain[cookie.host][cookie.path].unnamed[cookie.value]) delete this.cookiesByDomain[cookie.host][cookie.path].unnamed[cookie.value]; return; if (!this._db) this._openDB(); Components.utils.reportError("DELETE COOKIE : "+cookie.host+":"+cookie.path+" "+cookie.name);//+"/"+cookie.value); var statement; /*if (cookie.value) statement = this._db.createStatement("DELETE FROM cookies WHERE session_id=:session_id and name=:name and value=:value and host=:host and path=:path"); else*/ statement = this._db.createStatement("DELETE FROM cookies WHERE session_id=:session_id and name=:name and host=:host and path=:path"); statement.params.session_id = sessionId; statement.params.name = cookie.name; //if (cookie.value) // statement.params.value = cookie.value; statement.params.host = cookie.host; statement.params.path = cookie.path; statement.executeStep(); } catch(e) { try { Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError+"\n"+e); } catch(e) { Components.utils.reportError("exception : "+e); } } } CookiesStorage._updateBuffer = {}; CookiesStorage.updateCookie = function (sessionId, cookie) { if (!this._updateBuffer[sessionId]) this._updateBuffer[sessionId] = {}; if (!this._updateBuffer[sessionId][cookie.host]) this._updateBuffer[sessionId][cookie.host] = {}; if (!this._updateBuffer[sessionId][cookie.host][cookie.path]) this._updateBuffer[sessionId][cookie.host][cookie.path] = {}; this._updateBuffer[sessionId][cookie.host][cookie.path][cookie.name] = cookie; if (this._bufferTimeout) return; this._bufferTimeout = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); this._bufferTimeout.initWithCallback({ notify: function () { try { CookiesStorage._bufferTimeout = null; CookiesStorage._goUpdateBuffer(); log("CookiesStorage","Buffer saved!"); } catch(e) { Components.utils.reportError(e); } }}, 4000, Components.interfaces.nsITimer.TYPE_ONE_SHOT); } CookiesStorage._goUpdateBuffer = function () { if (!this._db) this._openDB(); if (!this.statementWithValue || !this.statementWithoutValue) { var insertSQL1 = ""; insertSQL1 += "INSERT OR REPLACE INTO cookies "; insertSQL1 += "(session_id, name, "; insertSQL1 += "host, path, expiry, lastAccessed, isSecure, isHttpOnly, isSession) "; insertSQL1 += "VALUES "; insertSQL1 += "(:session_id, :name, "; insertSQL1 += ":host, :path, :expiry, DATETIME('NOW'), :is_secure, :is_http_only, :is_session)"; var insertSQL2 = ""; insertSQL2 += "INSERT OR REPLACE INTO cookies "; insertSQL2 += "(session_id, name, "; insertSQL2 += "value, "; insertSQL2 += "host, path, expiry, lastAccessed, isSecure, isHttpOnly, isSession) "; insertSQL2 += "VALUES "; insertSQL2 += "(:session_id, :name, "; insertSQL2 += ":value, "; insertSQL2 += ":host, :path, :expiry, DATETIME('NOW'), :is_secure, :is_http_only, :is_session)"; this.statementWithoutValue = this._db.createStatement(insertSQL1); this.statementWithValue = this._db.createStatement(insertSQL2); } var start=new Date().getTime(); var count = 0; this._db.beginTransaction(); for(var session in this._updateBuffer) { var bySession = this._updateBuffer[session]; for(var host in bySession) { var byHost = bySession[host]; for(var path in byHost) { var byPath = byHost[path]; for(var name in byPath) { //log("CookiesStorage","save this : "+name); this._goUpdateOne(session, byPath[name]); count++; } } } } this._db.commitTransaction(); //Components.utils.reportError(count+" cookies updates in "+(new Date().getTime()-start)+"ms"); this._updateBuffer = {}; } CookiesStorage._goUpdateOne = function (sessionId, cookie) { try { var statement = cookie.value?this.statementWithValue:this.statementWithoutValue; statement.params.session_id = sessionId; statement.params.name = cookie.name; if (cookie.value) statement.params.value = cookie.value; statement.params.host = cookie.host; statement.params.path = cookie.path; statement.params.expiry = cookie.expiry?cookie.expiry:0; statement.params.is_secure = cookie.isSecure?1:0; statement.params.is_http_only = cookie.isHttpOnly?1:0; statement.params.is_session = cookie.isSession?1:0; statement.executeAsync(); } catch(e) { try { Components.utils.reportError(this._db.lastErrorString+" / "+this._db.lastError+"\n"+e); } catch(e) { Components.utils.reportError(e); } } } /////////////////////////////////////////////////////////////////////////// // Class CookiesDB //------------------------------------------------------------------------- // "Man is the middle" // Mean to be an instance of one independant Cookies database // Main object between website (http request/JS cookie attribute) and Cookies class/services // function CookiesDB(sessionId, onReadyCallback) { this._sessionId = sessionId; this.cookiesByDomain = {}; var _self = this; CookiesStorage.getCookiesForSession(sessionId, function (cookies) { for(var i=0; i<cookies.length; i++) { _self._addCookie(cookies[i]); } if (typeof onReadyCallback=="function") onReadyCallback(); }); } CookiesDB.prototype._addCookie = function (cookie) { if (!this.cookiesByDomain[cookie.host]) this.cookiesByDomain[cookie.host] = {}; if (!this.cookiesByDomain[cookie.host][cookie.path]) this.cookiesByDomain[cookie.host][cookie.path] = {named:{},unnamed:{}}; if (cookie.name && cookie.name.length>0) delete this.cookiesByDomain[cookie.host][cookie.path].named[cookie.name]; else delete this.cookiesByDomain[cookie.host][cookie.path].unnamed[cookie.value]; if (cookie.expiry && cookie.expiry <= new Date().getTime()) { //if (!cookie.isSession) // CookiesStorage.deleteCookie(this._sessionId, cookie); //return log("SetCondition","cookie expired!"); } else { //log("SetCondition",cookie.name+" not expired : "+cookie.expiry+" <= "+new Date().getTime()); } if (cookie.name && cookie.name.length>0) this.cookiesByDomain[cookie.host][cookie.path].named[cookie.name]=cookie; else this.cookiesByDomain[cookie.host][cookie.path].unnamed[cookie.value]=cookie; //if (!cookie.isSession) { CookiesStorage.updateCookie(this._sessionId, cookie); //} } CookiesDB.prototype._getCookiesForDomain = function (domain) { var byDomain = this.cookiesByDomain[domain]; //log("GetCondition","getCookiesForDomain("+domain+")="+(byDomain?byDomain.length:"null")); if (!byDomain) return []; var list=[]; for(var path in byDomain) { var byPath = byDomain[path]; for(var name in byPath.named) { list.push(byPath.named[name]) } for(var v in byPath.unnamed) { list.push(byPath.unnamed[v]) } } return list; } CookiesDB.prototype._getCookieString = function (hostFromURI, pathFromURI, isSecure, aHttpBound) { // JS equivalent of GetCookieInternal (part of) // http://mxr.mozilla.org/mozilla-central/source/netwerk/cookie/src/nsCookieService.cpp#1218 // trim trailing dots hostFromURI = hostFromURI.replace(/(^\.)|(\.$)/g,"") // insert a leading dot, so we begin the hash lookup with the // equivalent domain cookie host hostFromURI = "."+hostFromURI; var currentDot = hostFromURI; var nextDot = hostFromURI.substring(1); var foundCookieList = []; // begin hash lookup, walking up the subdomain levels. // we use nextDot to force a lookup of the original host (without leading dot). do { var list = this._getCookiesForDomain(currentDot); for (var i=0; i<list.length; i++) { var cookie = list[i]; if (!cookie.canGetFrom(pathFromURI, isSecure, aHttpBound)) continue; // all checks passed - add to list and check if lastAccessed stamp needs updating foundCookieList.push(cookie); } currentDot = nextDot; if (currentDot) { var temp = currentDot.substring(1); var idx = temp.indexOf('.'); if (idx==-1) nextDot = null; else nextDot = temp.substring(idx); } } while (currentDot && currentDot.length>0); if (foundCookieList.length == 0) return ""; // return cookies in order of path length; longest to shortest. // this is required per RFC2109. if cookies match in length, // then sort by creation time (see bug 236772). // foundCookieList.Sort(CompareCookiesForSendingComparator()); return Cookie.cookiesListToString(foundCookieList); } CookiesDB.prototype.getCookieStringFromJS = function (location) { var path = location.pathname; var idx = path.lastIndexOf('/'); if (idx!=-1) path = path.substring(0,idx); return this._getCookieString(location.host, path, location.protocol=="https:", false); } const ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService); CookiesDB.prototype.setCookieStringFromJS = function (location, str) { var uri = ioService.newURI(location.href, null, null); var iterator = new CookieStringIterator(str); var cookie = iterator.getNext(); if (cookie && cookie.canSetFrom(uri)) { cookie.computeExpiracy(new Date().getTime()); this._addCookie(cookie); } } CookiesDB.prototype.getCookieStringFromHttp = function (uri) { // get host and path from the nsIURI // note: there was a "check if host has embedded whitespace" here. // it was removed since this check was added into the nsIURI impl (bug 146094). var hostFromURI = uri.asciiHost; var pathFromURI = uri.path; // check if aHostURI is using an https secure protocol. // if it isn't, then we can't send a secure cookie over the connection. // if SchemeIs fails, assume an insecure connection, to be on the safe side var isSecure = uri.schemeIs("https"); return this._getCookieString(hostFromURI, pathFromURI, isSecure, true); } CookiesDB.prototype.setCookieStringFromHttp = function (dateHeader, cookiesHeader, uri) { var serverTime = new Date().getTime(); try { serverTime = Cookie.parseCookieDate(dateHeader).getTime(); } catch(e) {} var iterator = new CookieStringIterator(cookiesHeader); while(iterator.hasNext()) { var cookie = iterator.getNext(); if (cookie && cookie.canSetFrom(uri)) { cookie.computeExpiracy(serverTime); this._addCookie(cookie); } } } /////////////////////////////////////////////////////////////////////////// // Service MainRequestsObserver //------------------------------------------------------------------------- // Watch for all outgoing and ingoing HTTP requests // in order to replace cookies going out with cookies from our database // and to put cookies going in into our database // const MainRequestsObserver = {}; const nsILoadContext = Components.interfaces.nsILoadContext; MainRequestsObserver.findBrowserForRequest = function(channel) { // First, we need to find loadContext for this request // channel is an nsIHttpChannel which inherits from nsIChannel then from nsIRequest : // http://mxr.mozilla.org/mozilla-central/source/netwerk/base/public/nsIChannel.idl#60 // \--> notificationCallbacks // http://mxr.mozilla.org/mozilla-central/source/netwerk/base/public/nsIRequest.idl#50 // \--> loadGroup var loadContext; try { loadContext = channel.notificationCallbacks.getInterface(nsILoadContext); } catch (ex) { try { loadContext = channel.loadGroup.notificationCallbacks.getInterface(nsILoadContext); } catch (ex) { loadContext = null; } } // Some firefox internal request doesn't have loadGroup : // - safebrowsing requests // - search suggest queries // - ssl certificate verification // ... // But for web content, it appears that only this case doesn't have loadGroup : // - CSS Icons explicitely lets loadGroup empty : // http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsImageFrame.cpp#1653 if (!loadContext) { //log("FindBrowser","Unable to find load context for "+channel.URI.spec+"\n"); return null; } // Then we just have to retrieve associated window from the loadContext // http://mxr.mozilla.org/mozilla-central/source/docshell/base/nsILoadContext.idl var win = loadContext.associatedWindow; if (!win) { //log("FindBrowser","Unable to find associated window for "+channel.URI.spec+"\n"); return null; } // Now get the top xul ChromeWindow object with some QueryInterface magic // (all chrome requests fail here) var browserWindow; try { browserWindow = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIWebNavigation) .QueryInterface(Components.interfaces.nsIDocShellTreeItem) .rootTreeItem .QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIDOMWindow).wrappedJSObject; } catch(e) {} if (!browserWindow) { //log("FindBrowser","Unable to find top xul window "+channel.URI.spec+"\n"); return null; } // Need to iterate above windows to go through iframes up to the top content window while( win!=win.top ) { win = win.top; } // And finally, find the matching tab var browser; if (browserWindow.gBrowser) browser = browserWindow.gBrowser.getBrowserForDocument(win.document); if (browserWindow.opener && browserWindow.opener.ynWebPanel) browser = browserWindow.opener.ynWebPanel.getBrowser(); if (!browser) { //log("FindBrowser","Unable to find browser "+channel.URI.spec+"\n"); return null; } //log("FindBrowser","Found browser for "+channel.URI.spec+"\n"); return browser; } MainRequestsObserver.observe = function (channel, aTopic, aData) { try { if (aTopic == 'http-on-modify-request') { channel.QueryInterface(Components.interfaces.nsIHttpChannel); var browser = MainRequestsObserver.findBrowserForRequest(channel); if (!browser) return; if (!browser.__cookies && !browser.__cookiesChecked) { browser.__cookiesChecked = true; // try to restore cookies DB only once // First try to retrieve a cookie session id from session store if (CookiesService._listener) { var session_id = CookiesService._listener.getBrowserSessionId(browser); if (session_id) { CookiesService.attachSessionToBrowser(browser, session_id); } } // Then try to find if the current document is opened by another one // typically: javascript window.open calls! var openerWindow = browser.contentWindow.opener; var openerBrowser = CookiesProgressListener._findBrowserForContentWindow(openerWindow); if (openerBrowser && openerBrowser.__cookies) { Components.utils.reportError("sessionId from opener : "+openerBrowser.__cookies._sessionId); browser.__cookies = openerBrowser.__cookies; CookiesService._listener.onBrowserAttached(browser, browser.__cookies._sessionId); } } if (!browser.__cookies) { return; } // Get cookies from our DB and replace HTTP Cookie header var cookiesHeader = browser.__cookies.getCookieStringFromHttp(channel.URI); channel.setRequestHeader("Cookie",cookiesHeader,false); log("HTTP","SEND cookies : "+channel.URI.spec+"\n<<"+cookiesHeader+">>"); } else if (aTopic == 'http-on-examine-response') { channel.QueryInterface(Components.interfaces.nsIHttpChannel); var browser = MainRequestsObserver.findBrowserForRequest(channel); if (!browser) return; if (!browser.__cookies) { return; } // Get Set-Cookie header and update our cookie database var cookiesHeader; try { cookiesHeader = channel.getResponseHeader("Set-Cookie"); } catch(e) { cookiesHeader = null; } if (!cookiesHeader) return; log("HTTP","RECEIVE cookies : "+channel.URI.spec+"\n<<"+cookiesHeader+">>"); var dateHeader; try { dateHeader = channel.getResponseHeader("Date"); } catch(e){} browser.__cookies.setCookieStringFromHttp(dateHeader, cookiesHeader, channel.URI); // Reset cookie header in order to avoid mozilla to take account of these cookies channel.setResponseHeader("Set-Cookie", "", false); } } catch(e) {Components.utils.reportError(e);} } MainRequestsObserver.start = function () { var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService); observerService.addObserver(this, "http-on-modify-request", false); observerService.addObserver(this, "http-on-examine-response", false); } MainRequestsObserver.stop = function () { var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService); observerService.removeObserver(this, "http-on-modify-request", false); observerService.removeObserver(this, "http-on-examine-response", false); } /////////////////////////////////////////////////////////////////////////// // CookiesProgressListener //------------------------------------------------------------------------- // Watch for new tab or location change // in order to bind our self document.cookie attribute // which will retrieve cookies from our database // const CookiesProgressListener = {}; const nsIInterfaceRequestor = Components.interfaces.nsIInterfaceRequestor; CookiesProgressListener._findBrowserForContentWindow = function (win) { // Now get the top xul ChromeWindow object with some QueryInterface magic // (all chrome requests fail here) var browserWindow; try { browserWindow = win.QueryInterface(nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIWebNavigation) .QueryInterface(Components.interfaces.nsIDocShellTreeItem) .rootTreeItem .QueryInterface(nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIDOMWindow).wrappedJSObject; } catch(e) {} if (!browserWindow) { //log("FindBrowser","Unable to find top xul window "+channel.URI.spec+"\n"); return null; } // Need to iterate above windows to go through iframes up to the top content window while( win!=win.top ) { win = win.top; } // And finally, find the matching tab var browser; if (browserWindow.gBrowser) browser = browserWindow.gBrowser.getBrowserForDocument(win.document); if (browserWindow.opener && browserWindow.opener.ynWebPanel) browser = browserWindow.opener.ynWebPanel.getBrowser(); if (!browser) { //log("FindBrowser","Unable to find browser "+channel.URI.spec+"\n"); return null; } return browser; } CookiesProgressListener.bindCookiesAttribute = function(win) { if (win.___cookiesHooked) return; win.___cookiesHooked = true; log("NewContentDocument","URL : "+win.document.location); var browser = CookiesProgressListener._findBrowserForContentWindow(win); if (!browser) return; if (!browser.__cookies) { return; } var doc = win.document; doc.wrappedJSObject.__defineSetter__("cookie", function(v) { try { if (!browser.__cookies) return; log("JSCookie","SET from "+doc.location+"\n"+v); browser.__cookies.setCookieStringFromJS(doc.location, v); } catch(e) { Components.utils.reportError(e); } }); doc.wrappedJSObject.__defineGetter__("cookie", function() { try { if (!browser.__cookies) return; var s = browser.__cookies.getCookieStringFromJS(doc.location); log("JSCookie","GET from "+doc.location+"\n"+s); return s; } catch(e) { Components.utils.reportError(e); } }); } CookiesProgressListener.onLocationChange = function(webProgress, request, location) { //Components.utils.reportError("location change : "+(request?request.name:"")+" --- "+(location?location.href:"")); var win = webProgress.DOMWindow; if (!win) return; CookiesProgressListener.bindCookiesAttribute(win); } CookiesProgressListener.onProgressChange = function(webProgress, request, curSelfProgress) { //Components.utils.reportError("progress change : "+(request?request.name:"")+" --- "+curSelfProgress); } CookiesProgressListener.onSecurityChange = function(webProgress, request, state) { //Components.utils.reportError("security change : "+(request?request.name:"")+" --- "+state); } CookiesProgressListener.onStateChange = function(webProgress, request, stateFlags, status) { /* Components.utils.reportError("state change : "+(request?request.name:"")+" --- "+stateFlags); Components.utils.reportError("START="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_START)); Components.utils.reportError("REDIRECTING="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_REDIRECTING)); Components.utils.reportError("STATE_TRANSFERRING="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_TRANSFERRING)); Components.utils.reportError("STATE_NEGOTIATING="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_NEGOTIATING)); Components.utils.reportError("STATE_STOP="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_STOP)); Components.utils.reportError("STATE_IS_REQUEST="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_IS_REQUEST)); Components.utils.reportError("STATE_IS_DOCUMENT="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_IS_DOCUMENT)); Components.utils.reportError("STATE_IS_NETWORK="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_IS_NETWORK)); Components.utils.reportError("STATE_IS_WINDOW="+(stateFlags&Components.interfaces.nsIWebProgressListener.STATE_IS_WINDOW)); */ //if(stateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) { var win = webProgress.DOMWindow; if (!win) return; CookiesProgressListener.bindCookiesAttribute(win); //} } CookiesProgressListener.onStatusChange = function(webProgress, request, status, message) { //Components.utils.reportError("status change : "+(request?request.name:"")+" --- "+status+" - "+message); } CookiesProgressListener.QueryInterface = function(aIID) { if (aIID.equals(Components.interfaces.nsIWebProgressListener) || aIID.equals(Components.interfaces.nsISupportsWeakReference) || aIID.equals(Components.interfaces.nsIObserver) || aIID.equals(Components.interfaces.nsISupports)) return this; throw Components.results.NS_NOINTERFACE; } CookiesProgressListener.addProgressListener = function (prog) { prog.addProgressListener(CookiesProgressListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL); } CookiesProgressListener.removeProgressListener = function (prog) { prog.removeProgressListener(CookiesProgressListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL); } /////////////////////////////////////////////////////////////////////////// // Service FirefoxWindowsWatcher //------------------------------------------------------------------------- // Utils for Firefox, // Automatically register all browsers to CookiesProgressListener // var FirefoxWindowsWatcher = {}; FirefoxWindowsWatcher.QueryInterface = function(aIID) { if (aIID.equals(Components.interfaces.nsISupportsWeakReference) || aIID.equals(Components.interfaces.nsIObserver) || aIID.equals(Components.interfaces.nsISupports)) return this; throw Components.results.NS_NOINTERFACE; } FirefoxWindowsWatcher._onWindowLoad = function (win) { CookiesProgressListener.addProgressListener(win.gBrowser); // Watch for website inner link clicks var Ci = Components.interfaces; var previous = win.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow; win.QueryInterface(Components.interfaces.nsIDOMChromeWindow).browserDOMWindow = { //QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]), openURI: function (aURI, aOpener, aWhere, aContext) { //Components.utils.reportError("openURI"); var newWindow = previous.openURI(aURI, aOpener, aWhere, aContext); try { //Components.utils.reportError("newWindow : "+newWindow); var openerBrowser; if (aOpener) openerBrowser = CookiesProgressListener._findBrowserForContentWindow(aOpener); //Components.utils.reportError("openerBrowser : "+openerBrowser); if (openerBrowser && openerBrowser.__cookies) { var newBrowser = CookiesProgressListener._findBrowserForContentWindow(newWindow); //Components.utils.reportError("newBrowser : "+newBrowser); if (newBrowser) { Components.utils.reportError("sessionId : "+openerBrowser.__cookies._sessionId); newBrowser.__cookies = openerBrowser.__cookies; //var reloadFlags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Components.interfaces.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE; //newBrowser.reload(reloadFlags); CookiesService._listener.onBrowserAttached(newBrowser, newBrowser.__cookies._sessionId); } } }catch(e){ Components.utils.reportError(e); } return newWindow; }, isTabContentWindow: function (aWindow) { return previous.isTabContentWindow(aWindow); } }; // Watch for context menu->open in tab var previousOpenNewTabWith = win.openNewTabWith; win.openNewTabWith = function (aURL, aDocument, aPostData, aEvent, aAllowThirdPartyFixup, aReferrer) { //Components.utils.reportError("openNewTabWith"); var openerBrowser; var newTab; if (aDocument && aDocument.defaultView) openerBrowser = CookiesProgressListener._findBrowserForContentWindow(aDocument.defaultView); if (openerBrowser && openerBrowser.__cookies) { newTab = previousOpenNewTabWith.call(win, "about:blank", aDocument, null, null, null, null); var newBrowser; if (newTab && newTab.linkedBrowser) newBrowser = newTab.linkedBrowser; if (newBrowser) { //Components.utils.reportError("sessionId from new tab : "+openerBrowser.__cookies._sessionId); newBrowser.__cookies = openerBrowser.__cookies; CookiesService._listener.onBrowserAttached(newBrowser, newBrowser.__cookies._sessionId); var charsetArg = null; var wintype = newBrowser.ownerDocument.documentElement.getAttribute("windowtype"); if (wintype == "navigator:browser") charsetArg = "charset=" + newBrowser.ownerDocument.defaultView.content.document.characterSet; var referrerURI = aDocument ? aDocument.documentURIObject : aReferrer; if (aPostData === undefined) aPostData = null; var flags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE; if (aAllowThirdPartyFixup) { flags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; } newBrowser._originalLoadURIWithFlags(aURL, flags, referrerURI, charsetArg, aPostData); } } else { newTab = previousOpenNewTabWith.call(win, aURL, aDocument, aPostData, aEvent, aAllowThirdPartyFixup, aReferrer); } return newTab; }; // Watch for context menu->open in window var previousOpenNewWindowWith = win.openNewWindowWith; win.openNewWindowWith = function (aURL, aDocument, aPostData, aAllowThirdPartyFixup, aReferrer) { //Components.utils.reportError("openNewWindowWith"); var newWindow; var openerBrowser; if (aDocument && aDocument.defaultView) openerBrowser = CookiesProgressListener._findBrowserForContentWindow(aDocument.defaultView); if (openerBrowser && openerBrowser.__cookies) { newWindow = previousOpenNewWindowWith.call(win, "about:blank", aDocument, null, null, null); newWindow.addEventListener("load",function () { newWindow.removeEventListener("load",arguments.callee,false); var newBrowser; if (newWindow && newWindow.gBrowser && newWindow.gBrowser.mCurrentBrowser) newBrowser = newWindow.gBrowser.mCurrentBrowser; if (newBrowser) { //Components.utils.reportError("sessionId : "+openerBrowser.__cookies._sessionId); newBrowser.__cookies = openerBrowser.__cookies; CookiesService._listener.onBrowserAttached(newBrowser, newBrowser.__cookies._sessionId); var charsetArg = null; var wintype = newBrowser.ownerDocument.documentElement.getAttribute("windowtype"); if (wintype == "navigator:browser") charsetArg = "charset=" + newBrowser.ownerDocument.defaultView.content.document.characterSet; var referrerURI = aDocument ? aDocument.documentURIObject : aReferrer; if (aPostData === undefined) aPostData = null; var flags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE; if (aAllowThirdPartyFixup) { flags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; } newBrowser._originalLoadURIWithFlags(aURL, flags, referrerURI, charsetArg, aPostData); } }, false); } else { newWindow = previousOpenNewWindowWith.call(win, aURL, aDocument, aPostData, aAllowThirdPartyFixup, aReferrer); } return newWindow; }; } FirefoxWindowsWatcher.observe = function (aSubject, aTopic, aData) { if (aTopic == "domwindowopened") { var win = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow); if (!win.gBrowser) return; var type = win.document.documentElement.getAttribute("windowtype"); if (!type) { win.addEventListener("load",function () { win.removeEventListener("load", arguments.callee, false); if (win.document.documentElement.getAttribute("windowtype") != "navigator:browser") return; FirefoxWindowsWatcher._onWindowLoad(win); }, false); } else if (type == "navigator:browser") { FirefoxWindowsWatcher._onWindowLoad(win); } } else if (aTopic == "domwindowclosed") { var win = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow); if (win.document.documentElement.getAttribute("windowtype") != "navigator:browser") return; if (!win.gBrowser) return; CookiesProgressListener.removeProgressListener(win.gBrowser); } } FirefoxWindowsWatcher.start = function () { var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] .getService(Components.interfaces.nsIWindowWatcher); var enumerator = ww.getWindowEnumerator(); while (enumerator.hasMoreElements()) { FirefoxWindowsWatcher.observe(enumerator.getNext(),"domwindowopened",null); } ww.registerNotification(this); } FirefoxWindowsWatcher.stop = function () { var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] .getService(Components.interfaces.nsIWindowWatcher); ww.unregisterNotification(this); var enumerator = ww.getWindowEnumerator(); while (enumerator.hasMoreElements()) { FirefoxWindowsWatcher.observe(enumerator.getNext(),"domwindowclosed",null); } } //////////////////////////////////////////////////////////////////////////////// // JS Module API // const CookiesService = { _listener : null, FirefoxWindowsWatcher : FirefoxWindowsWatcher, init : function (listener) { try { if (typeof listener!="object") throw "CookiesService.init: listener is mandatory"; if (typeof listener.getBrowserSessionId!="function") throw "CookiesService.init: listener need to have a getBrowserSessionId function"; if (typeof listener.onBrowserAttached!="function") throw "CookiesService.init: listener need to have a onBrowserAttached function"; this._listener = listener; MainRequestsObserver.start(); } catch(e) { Components.utils.reportError(e); } }, // Register a new <browser> object // (Allow to catch JS getter/setter of cookies) registerBrowser : function (browser) { var req = browser.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor); var prog = req.getInterface(Components.interfaces.nsIWebProgress); CookiesProgressListener.addProgressListener(prog); }, unregisterBrowser : function (browser) { var req = browser.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor); var prog = req.getInterface(Components.interfaces.nsIWebProgress); CookiesProgressListener.removeProgressListener(prog); }, renameSession : function (src_session_id, dst_session_id) { CookiesStorage.renameSession(src_session_id, dst_session_id); }, setSessionInfo : function (session_id, user_name, user_avatar, user_network_name, user_network_id) { CookiesStorage.setSessionInfo(session_id, user_name, user_avatar, user_network_name, user_network_id); }, getSessionInfo : function (browser, callback) { if (browser.__cookies && browser.__cookies._sessionId) CookiesStorage.getSessionInfo(browser.__cookies._sessionId, callback); else if (typeof callback=="function") callback(); }, deleteSessionInfo : function (session_id) { CookiesStorage.deleteSession(session_id); }, getSessionId : function (browser) { if (browser && browser.__cookies && browser.__cookies._sessionId!="undefined") return browser.__cookies._sessionId; return null; }, getAllSessions : function (callback) { CookiesStorage.getAllSessions(callback); }, attachSessionToBrowser : function (browser, session_id, onReadyCallback) { browser.__cookies = new CookiesDB(session_id, onReadyCallback); CookiesService._listener.onBrowserAttached(browser, session_id); }, detachSessionFromBrowser : function (browser) { browser.__cookies = null; CookiesService._listener.onBrowserDetached(browser); }, addCustomProfile : function (name, home, avatar, callback) { if (!avatar || avatar.length==0) { try { var faviconService = Components.classes["@mozilla.org/browser/favicon-service;1"].getService(Components.interfaces.nsIFaviconService); var IOService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService); var uri = IOService.newURI(home, null, null); //avatar = faviconService.getFaviconForPage(uri).spec; avatar = uri.prePath + "/favicon.ico"; Components.utils.reportError(home+" --> "+avatar); } catch(e) { Components.utils.reportError(e); } } var id = "custom_"+new Date().getTime(); CookiesStorage.setSessionInfo(id, name, avatar, null, null, home); if (typeof callback=="function") callback(id); } };